/*
 * Copyright (c) 2008-2018, RF-Embedded GmbH
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 *  1. Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice, 
 *     this list of conditions and the following disclaimer in the 
 *     documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;

namespace CSrfeReaderInterface.impl.device
{
    /// <summary>
    /// Implementaion of a serial device
    /// </summary>
    public class SerialDevice : CSrfeReaderInterface.device.IProtocolDeviceInterface
    {
        private SerialPort m_port;

        /// <summary>
        /// Constructs a new serial device
        /// </summary>
        /// <param name="portName">The port name to use</param>
        public SerialDevice(string portName)
        {
            // Create a new SerialPort object with default settings.
            m_port = new SerialPort();

            // Allow the user to set the appropriate properties.
            m_port.PortName = portName;
            m_port.BaudRate = 115200;
            m_port.Parity = Parity.None;
            m_port.DataBits = 8;
            m_port.StopBits = StopBits.One;
            m_port.Handshake = Handshake.None;

            //            // Set the read/write timeouts
            //            m_port.ReadTimeout = 1; // TODO: test -> default -1
            //            m_port.WriteTimeout = 1; // TODO: test -> default -1

            m_port.DataReceived += new SerialDataReceivedEventHandler(DataReceived);

        }

        /// <summary>
        /// Returns the used port name
        /// </summary>
        /// <returns>The used port name</returns>
        public string PortName()
        {
            return m_port.PortName;
        }

        /// <summary>
        /// Opens a serial device connection
        /// </summary>
        /// <returns>Success of the operation</returns>
        public override bool Open()
        {
            try
            {
                m_port.Open();
            }
            catch (Exception)
            {
                return false;
            }

            if (m_port.PortName.Contains("/dev/tty"))
            {
                Task.Factory.StartNew(() => {
                    ReadTask();
                });
            }

            return m_port.IsOpen;
        }

        /// <summary>
        /// Closes a serial device connection
        /// </summary>
        /// <returns>Success of the operation</returns>
        public override bool Close()
        {
            try
            {
                m_port.Close();
            }
            catch (Exception)
            {
                return false;
            }
            return !m_port.IsOpen;
        }

        /// <summary>
        /// Sends data to the reader connected via serial device connection
        /// </summary>
        /// <param name="data">Data to be sent</param>
        /// <returns>Success of the operation</returns>
        public override bool Send(byte[] data)
        {
            try
            {
                m_port.Write(data, 0, data.Length);
            }
            catch (Exception)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Implementation of a Read Task in case the application is ran using mono
        /// </summary>
        private void ReadTask()
        {
            while(m_port.IsOpen)
            {
                if(m_port.BytesToRead > 0)
                {
                    DataReceived(null, null);
                }

                Thread.Sleep(5);
            }
        }

        /// <summary>
        /// Event handler for received data
        /// </summary>
        /// <param name="sender">Sender of the event</param>
        /// <param name="e">Arguments</param>
        private void DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            byte[] buffer = new byte[m_port.BytesToRead];
            try
            {
                m_port.Read(buffer, 0, buffer.Length);
            }
            catch (Exception)
            {
                return;
            }
            RaiseDataReadEvent(buffer);
        }
    }
}
